/**
 * @author Tin Pham / 7inpham@gmail.com
 * Application create a renderer corresponding to the provided DOM attributes.
 * It holds a stage containing all active game elements loaded, created and organized in memory.
 */
Tinz.Application = function (ref, width, height) {
	this.container = {
		dom : document.getElementById(ref),
		width : width,
		height : height
	};
	this.renderer = new THREE.WebGLRenderer({
			antialias : true
		});
	this.renderer.setSize(this.container.width, this.container.height);
	this.renderer.setClearColor(0xffffff, 1);
	this.renderer.shadowMapEnabled = true;
	this.renderer.shadowMapSoft = true;
	// Append the renderer into the container
	this.container.dom.appendChild(this.renderer.domElement);
	// Global configurations
	this.configurations = {
		world : 'Room',
		keyCodes : {
			left : 65, // A
			forward : 87, // W
			right : 68, // D
			backward : 83 // S
		},
		activeCamera : 'orbitCamera',
		debug : true
	};
	this.clock = new THREE.Clock();
	this.stage = {};
	this.stage.scene = {};
	this.stage.character = {};
	this.stage.cameras = {
		chaseCamera : new THREE.PerspectiveCamera(45, 1, 0.01, 10000),
		orbitCamera : new THREE.PerspectiveCamera(45, 1, 0.01, 10000)
	};
	// Add the orbit orbitControls
	this.stage.orbitControls = new THREE.OrbitControls(this.stage.cameras.orbitCamera, this.renderer.domElement);
	this.stage.orbitControls.target = new THREE.Vector3(0, 100, 0);
	this._setOrbitCameraLookAt();
	this.stage.assets = [];

	this.stage.helpers = {};
	this.stage.helpers.axes = new THREE.AxisHelper(500);

	this.stage.helpers.gridXZ = new THREE.GridHelper(300, 10);
	this.stage.helpers.gridXZ.setColors(new THREE.Color(0x006600), new THREE.Color(0x006600));
	this.stage.helpers.gridXZ.position.set(300, 0, 300);

	this.stage.helpers.gridXY = new THREE.GridHelper(300, 10);
	this.stage.helpers.gridXY.position.set(300, 300, 0);
	this.stage.helpers.gridXY.rotation.x = Math.PI / 2;
	this.stage.helpers.gridXY.setColors(new THREE.Color(0x000066), new THREE.Color(0x000066));

	this.stage.helpers.gridXZ = new THREE.GridHelper(300, 10);
	this.stage.helpers.gridXZ.position.set(0, 300, 300);
	this.stage.helpers.gridXZ.rotation.z = Math.PI / 2;
	this.stage.helpers.gridXZ.setColors(new THREE.Color(0x660000), new THREE.Color(0x660000));

	// direction (normalized), origin, length, color(hex)
	var origin = new THREE.Vector3(50, 300, 50);
	var terminus = new THREE.Vector3(75, 75, 75);
	var direction = new THREE.Vector3().subVectors(terminus, origin).normalize();
	this.stage.helpers.arrow = new THREE.ArrowHelper(direction, origin, 50, 0x884400);

	this._createSettingControls();
	this._registerInputEventHandler();
	this._registerResizeHandler();
};

/**
 * Draw menu screen and wait for user action
 */
Tinz.Application.prototype.initStage = function (value) {
	// Destroy
	this.stage.assets = [];

	var _this = this;
	var assetsToLoad = {
		models : [
			'assets/models/blade/blade.js'
		],
		textures : [
			'assets/models/blade/blade.jpg',
			'assets/textures/terrain.jpg'
		],
		images : [
			'assets/textures/heightmap_128.jpg'
		],
		total : 3
	};
	var count = 0;
	function onAssetLoaded(object) {
		_this.stage.assets.push(object);
		count++;
		if (count == assetsToLoad.total) {
			if (value == 'Room') {
				_this.stage.scene = new Tinz.RoomWorld();
				_this.stage.character = new Tinz.Monster(_this.stage.assets[0], _this.stage.assets[1]);
				_this.stage.scene.add(_this.stage.character.mesh);
			} else if (value == 'Garden') {
				_this.stage.scene = new Tinz.GardenWorld(_this.stage.assets);
				_this.stage.character = new Tinz.Character();
				_this.stage.scene.add(_this.stage.character.mesh);
			} else if (value == 'Island') {
				_this.stage.scene = new Tinz.IslandWorld(_this.stage.assets);
				_this.stage.character = new Tinz.Character();
				_this.stage.scene.add(_this.stage.character.mesh);
			}
			var __this = _this;
			function loop() {
				__this._updateAndRender();
				requestAnimationFrame(loop);
			}
			loop();
		}
	}
	var loader = new THREE.JSONLoader();
	for (var i = 0; i < assetsToLoad.models.length; i++) {
		loader.load(assetsToLoad.models[i], onAssetLoaded);
	}
	for (var i = 0; i < assetsToLoad.textures.length; i++) {
		THREE.ImageUtils.loadTexture(assetsToLoad.textures[i], null, onAssetLoaded);
	}

};

/**
 * Update and render the active stage
 */
Tinz.Application.prototype._updateAndRender = function () {
	if (this.configurations.debug) {
		this.stage.scene.add(this.stage.helpers.axes);
		//this.stage.scene.add(this.stage.helpers.gridXZ);
		//this.stage.scene.add(this.stage.helpers.gridXY);
		//this.stage.scene.add(this.stage.helpers.gridXZ);
		//this.stage.scene.add(this.stage.helpers.arrow);
	} else {
		//this.stage.scene.remove(this.stage.helpers.axes);
		//this.stage.scene.remove(this.stage.helpers.gridXZ);
		//this.stage.scene.remove(this.stage.helpers.gridXY);
		//this.stage.scene.remove(this.stage.helpers.gridXZ);
		//this.stage.scene.remove(this.stage.helpers.arrow);
	}
	/*
	if (this.configurations.world == 'Island') {
	this.stage.character.mesh.position.y = THREEx.Terrain.planeToHeightMapCoords(this.stage.scene.heightMap, this.stage.scene.ground, this.stage.character.mesh.position.x, this.stage.character.mesh.position.z) + 45;
	} else if (this.configurations.world == 'Room') {
	// Update the directions if we intersect with an obstacle
	var collisions, i,
	// Maximum distance from the origin before we consider collision
	distance = 32,
	// Get the obstacles array from our world
	obstacles = this.stage.scene.getObstacles();
	// For each ray
	for (i = 0; i < this.stage.character.rays.length; i += 1) {
	// We reset the raycaster to this direction
	this.stage.character.caster.set(this.stage.character.mesh.position, this.stage.character.rays[i]);
	// Test if we intersect with any obstacle mesh
	collisions = this.stage.character.caster.intersectObjects(obstacles);
	// And disable that direction if we do
	if (collisions.length > 0 && collisions[0].distance <= distance) {
	// Yep, this.rays[i] gives us : 0 => up, 1 => up-left, 2 => left, ...
	if ((i === 0 || i === 1 || i === 7) && this.stage.character.direction.forward) {
	this.stage.character.direction.forward = false;
	} else if ((i === 3 || i === 4 || i === 5) && !this.stage.character.direction.forward) {
	this.stage.character.direction.forward = false;
	}
	if ((i === 1 || i === 2 || i === 3) && this.stage.character.direction.backward) {
	this.stage.character.direction.backward = false;
	} else if ((i === 5 || i === 6 || i === 7) && !this.stage.character.direction.backward) {
	this.stage.character.direction.backward = false;
	}
	}
	}
	}
	 */
	this.stage.character.update(this.clock.getDelta());
	if (this.configurations.activeCamera == 'chaseCamera') {
		this._setChaseCameraLookAt(this.stage.character.mesh); // Set the camera to look at our character
		this.renderer.render(this.stage.scene, this.stage.cameras.chaseCamera);
	} else if (this.configurations.activeCamera == 'orbitCamera') {
		this.stage.orbitControls.update();
		this.renderer.render(this.stage.scene, this.stage.cameras.orbitCamera);
	} else {
		console.log('Not supported camera');
	}
};

// Updating the camera to follow and look at a given Object3D / Mesh
Tinz.Application.prototype._setChaseCameraLookAt = function (object) {
	var relativeCameraOffset = new THREE.Vector3(0, 250, -550);
	var cameraOffset = relativeCameraOffset.applyMatrix4(object.matrixWorld);
	this.stage.cameras.chaseCamera.position.x = cameraOffset.x;
	this.stage.cameras.chaseCamera.position.y = cameraOffset.y;
	this.stage.cameras.chaseCamera.position.z = cameraOffset.z;
	this.stage.cameras.chaseCamera.lookAt(object.position);
};

Tinz.Application.prototype._setOrbitCameraLookAt = function (object) {
	this.stage.cameras.orbitCamera.position.set(0, 600, 0);
	this.stage.cameras.orbitCamera.lookAt(this.stage.scene);
};

// Initilize GUI and attach its handlers
Tinz.Application.prototype._createSettingControls = function () {
	var app = this;
	var gui = new dat.GUI({
			height : 5 * 32 - 1
		});
	var folderSize = gui.addFolder('Window Settings');
	folderSize.add(this.container, 'width', 120).name('Width').onChange(function (value) {
		app.renderer.setSize(value, app.container.height);
	});
	folderSize.add(this.container, 'height', 100).name('Height').onChange(function (value) {
		app.renderer.setSize(app.container.width, value);
	});
	var folderKeys = gui.addFolder('Input Settings');
	folderKeys.add(this.configurations.keyCodes, 'left').name('Left');
	folderKeys.add(this.configurations.keyCodes, 'right').name('Right');
	folderKeys.add(this.configurations.keyCodes, 'forward').name('Forward');
	folderKeys.add(this.configurations.keyCodes, 'backward').name('Backward');
	gui.add(this.configurations, 'debug').name('Enable Debug Mode').onChange(function (value) {
		if (value) {
			console.log('Enabled debug mode.');
		} else {
			console.log('Disabled debug mode.');
		}
	});
	gui.add(this.configurations, 'activeCamera', ['chaseCamera', 'orbitCamera']).name('Active Camera').onChange(function (value) {
		//app._setOrbitCameraLookAt(app.character.mesh);
	});
	gui.add(this.configurations, 'world', ['Room', 'Garden', 'Island']).name('World').onChange(function (value) {
		app.initStage(value);
	});
};

/**
 * Register input event handlers
 */
Tinz.Application.prototype._registerInputEventHandler = function () {
	var _this = this; // Within below methods, we won't be able to access "this"
	var direction = {
		left : false,
		backward : false,
		right : false,
		forward : false
	};
	document.addEventListener("keydown", function (e) {
		var prevent = true;
		switch (e.keyCode) {
		case _this.configurations.keyCodes.left:
			direction.left = true;
			break;
		case _this.configurations.keyCodes.backward:
			direction.backward = true;
			break;
		case _this.configurations.keyCodes.right:
			direction.right = true;
			break;
		case _this.configurations.keyCodes.forward:
			direction.forward = true;
			break;
		default:
			prevent = false;
		}
		// Avoid the browser to react unexpectedly
		if (prevent) {
			e.preventDefault();
		}
		_this.stage.character.setDirection(direction); // Set the character's direction
	}, false);
	// When the character releases a key
	document.addEventListener("keyup", function (e) {
		var prevent = true;
		switch (e.keyCode) {
		case _this.configurations.keyCodes.left:
			direction.left = false;
			break;
		case _this.configurations.keyCodes.backward:
			direction.backward = false;
			break;
		case _this.configurations.keyCodes.right:
			direction.right = false;
			break;
		case _this.configurations.keyCodes.forward:
			direction.forward = false;
			break;
		default:
			prevent = false;
		}
		if (prevent) {
			e.preventDefault();
		}
		_this.stage.character.setDirection(direction);
	});
};

/**
 * Redefine the size of the renderer on window resize event
 */
Tinz.Application.prototype._registerResizeHandler = function () {
	var that = this;
	window.addEventListener('resize', function () {
		//_this.setAspect();
	}, false);
};
